/*! \file
 * \brief Enter a brief description here.
 *
 * Enter a detailed description here. (optional)
 * \author Author (Ivo Wingelaar)
 * \date Date (2011)
 * \version Version (0.0.1)
 * \copyright Copyright (GNU LGPL v3)
 */

#include "LynxWindowManagerLinux_C.hpp"

#ifdef _LYNX_LINUX_

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <sys/utsname.h>
#include <time.h>
#include "LynxEventReceiver.hpp"
#include <X11/XKBlib.h>
#include <X11/Xatom.h>
#include "LynxOSManagerLinux_C.hpp"
#include "LynxCursorManagerLinux_C.hpp"
#include "LynxTimeManagerLinux_C.hpp"
#include "LynxVideoManager_C.hpp"

namespace
{
Atom X_ATOM_CLIPBOARD;
Atom X_ATOM_TARGETS;
Atom X_ATOM_UTF8_STRING;
Atom X_ATOM_TEXT;
};

const char* wmDeleteWindow = "WM_DELETE_WINDOW";

namespace Lynx
{

//! constructor
WindowManagerLinux_C::WindowManagerLinux_C(const WindowParameters& parameters) :
    WindowManagerStub_C(parameters),
    mDisplay(0),
    mVisual(0),
    mScreennr(0),
    mWindow(0),
    mStdHints(0),
    mSoftwareImage(0),
    mGlxWin(0),
    mContext(0),
    mWidth(parameters.mWindowSize.X),
    mHeight(parameters.mWindowSize.Y),
    mWindowHasFocus(false),
    mWindowMinimized(false),
    mUseXVidMode(false),
    mUseXRandR(false),
    mUseGLXWindow(false),
    mAutorepeatSupport(0)
{
    mOSManager = new OSManagerLinux_C(this);
    mTimeManager = new TimeManagerLinux_C();

    // create keymap
    create_keymap();

    if(!create_window())
        return;

    // create cursor control
    mCursorManager = new CursorManagerLinux_C(&mDisplay, &mWindow, &mWidth, &mHeight);

    // create driver
    create_driver();
}

//! destructor
WindowManagerLinux_C::~WindowManagerLinux_C()
{
    if(mStdHints)
        XFree(mStdHints);
    // Disable cursor and free it later on
    mCursorManager->set_visible(false);
    if(mDisplay)
    {
        if(mContext)
        {
            if(mGlxWin)
            {
                if(!glXMakeContextCurrent(mDisplay, None, None, NULL))
                    mLogManager->log("Could not release glx context.", ELL_WARNING);
            }
            else
            {
                if(!glXMakeCurrent(mDisplay, None, NULL))
                    mLogManager->log("Could not release glx context.", ELL_WARNING);
            }
            glXDestroyContext(mDisplay, mContext);
            if(mGlxWin)
                glXDestroyWindow(mDisplay, mGlxWin);
        }

        // Reset fullscreen resolution change
        switch_to_fullscreen(true);

        XDestroyWindow(mDisplay,mWindow);
        XCloseDisplay(mDisplay);
    }
    if(mVisual)
        XFree(mVisual);
}

//! runs the device. Returns false if device wants to be deleted
bool WindowManagerLinux_C::run()
{
    ((TimeManagerStub_C*)mTimeManager)->tick();

    if(mDisplay)
    {
        while(XPending(mDisplay) > 0 && !mClose)
        {
            XEvent event;
            XNextEvent(mDisplay, &event);
            switch(event.type)
            {
            case ConfigureNotify:
                // check for changed mWindow size
                if((event.xconfigure.width != (int) mWidth) ||
                        (event.xconfigure.height != (int) mHeight))
                {
                    mWidth = event.xconfigure.width;
                    mHeight = event.xconfigure.height;

                    if(mVideoManager)
                        mVideoManager->on_resize( {mWidth, mHeight});
                }
                break;

            case MapNotify:
                mWindowMinimized=false;
                break;

            case UnmapNotify:
                mWindowMinimized=true;
                break;

            case FocusIn:
                mWindowHasFocus=true;
                break;

            case FocusOut:
                mWindowHasFocus=false;
                break;

            case MotionNotify:
            {
                MouseInput LynxEvent;
                LynxEvent.mEvent = EME_MOUSE_MOVED;
                LynxEvent.mX = event.xbutton.x;
                LynxEvent.mY = event.xbutton.y;
                LynxEvent.mControl = (event.xkey.state & ControlMask) != 0;
                LynxEvent.mShift = (event.xkey.state & ShiftMask) != 0;

                // mouse button states
                LynxEvent.mButtonStates = 0xffffffff;
                LynxEvent.mButtonStates = (event.xbutton.state & Button1Mask) ? EMBSM_LEFT : 0;
                LynxEvent.mButtonStates |= (event.xbutton.state & Button3Mask) ? EMBSM_RIGHT : 0;
                LynxEvent.mButtonStates |= (event.xbutton.state & Button2Mask) ? EMBSM_MIDDLE : 0;

                post_event_from_user(LynxEvent);
                break;
            }

            case ButtonPress:
            case ButtonRelease:
            {
                MouseInput LynxEvent;
                LynxEvent.mX = event.xbutton.x;
                LynxEvent.mY = event.xbutton.y;
                LynxEvent.mControl = (event.xkey.state & ControlMask) != 0;
                LynxEvent.mShift = (event.xkey.state & ShiftMask) != 0;

                // mouse button states
                // This sets the state which the buttons had _prior_ to the event.
                // So unlike on Windows the button which just got changed has still the old state here.
                // We handle that below by flipping the corresponding bit later.
                LynxEvent.mButtonStates = (event.xbutton.state & Button1Mask) ? EMBSM_LEFT : 0;
                LynxEvent.mButtonStates |= (event.xbutton.state & Button3Mask) ? EMBSM_RIGHT : 0;
                LynxEvent.mButtonStates |= (event.xbutton.state & Button2Mask) ? EMBSM_MIDDLE : 0;

                LynxEvent.mEvent = EME_COUNT;

                switch(event.xbutton.button)
                {
                case  Button1:
                    LynxEvent.mEvent =
                        (event.type == ButtonPress) ? EME_LMOUSE_PRESSED_DOWN : EME_LMOUSE_LEFT_UP;
                    LynxEvent.mButtonStates ^= EMBSM_LEFT;
                    break;

                case  Button3:
                    LynxEvent.mEvent =
                        (event.type == ButtonPress) ? EME_RMOUSE_PRESSED_DOWN : EME_RMOUSE_LEFT_UP;
                    LynxEvent.mButtonStates ^= EMBSM_RIGHT;
                    break;

                case  Button2:
                    LynxEvent.mEvent =
                        (event.type == ButtonPress) ? EME_MMOUSE_PRESSED_DOWN : EME_MMOUSE_LEFT_UP;
                    LynxEvent.mButtonStates ^= EMBSM_MIDDLE;
                    break;

                case  Button4:
                    if(event.type == ButtonPress)
                    {
                        LynxEvent.mEvent = EME_MOUSE_WHEEL;
                        LynxEvent.mWheel = 1.0f;
                    }
                    break;

                case  Button5:
                    if(event.type == ButtonPress)
                    {
                        LynxEvent.mEvent = EME_MOUSE_WHEEL;
                        LynxEvent.mWheel = -1.0f;
                    }
                    break;
                }

                if(LynxEvent.mEvent != EME_COUNT)
                {
                    post_event_from_user(LynxEvent);

                    if(LynxEvent.mEvent >= EME_LMOUSE_PRESSED_DOWN && LynxEvent.mEvent <= EME_MMOUSE_PRESSED_DOWN)
                    {
                        uint32_t clicks = check_successive_clicks(LynxEvent.mX, LynxEvent.mY, LynxEvent.mEvent);
                        if(clicks == 2)
                        {
                            LynxEvent.mEvent = (LYNX_E_MOUSE_EVENT)(EME_LMOUSE_DOUBLE_CLICK + LynxEvent.mEvent-EME_LMOUSE_PRESSED_DOWN);
                            post_event_from_user(LynxEvent);
                        }
                        else if(clicks == 3)
                        {
                            LynxEvent.mEvent = (LYNX_E_MOUSE_EVENT)(EME_LMOUSE_TRIPLE_CLICK + LynxEvent.mEvent-EME_LMOUSE_PRESSED_DOWN);
                            post_event_from_user(LynxEvent);
                        }
                    }
                }
                break;
            }


            case MappingNotify:
                XRefreshKeyboardMapping(&event.xmapping) ;
                break;

            case KeyRelease:
                if(0 == mAutorepeatSupport && (XPending(mDisplay) > 0))
                {
                    // check for Autorepeat manually
                    // We'll do the same as Windows does: Only send KeyPressed
                    // So every KeyRelease is a real release
                    XEvent next_event;
                    XPeekEvent(event.xkey.display, &next_event);
                    if((next_event.type == KeyPress) &&
                            (next_event.xkey.keycode == event.xkey.keycode) &&
                            (next_event.xkey.time - event.xkey.time) < 2)	// usually same time, but on some systems a difference of 1 is possible
                    {
                        /* Ignore the key release event */
                        break;
                    }
                }
                // fall-through in case the release should be handled
            case KeyPress:
            {
                SKeyMap mp;
                char buf[8]= {0};
                KeyInput LynxEvent;
                XLookupString(&event.xkey, buf, sizeof(buf), &mp.mX11Key, NULL);

                std::vector<SKeyMap>::iterator it = find(mKeyMap.begin(), mKeyMap.end(), mp);

                if(it != mKeyMap.end())
                {
                    LynxEvent.mKey = (LYNX_E_KEY_CODE)(*it).mWin32Key;
                }
                else
                {
                    LynxEvent.mKey = (LYNX_E_KEY_CODE)0;
                    char tmpbuf[255];
                    snprintf(tmpbuf, 255, "%d", (int)mp.mX11Key);
                    mLogManager->log("Could not find win32 key for x11 key.", tmpbuf, ELL_WARNING);
                }
                LynxEvent.mChar = ((buf))[0];
                LynxEvent.mPressedDown = (event.type == KeyPress);
                LynxEvent.mControl = (event.xkey.state & ControlMask) != 0;
                LynxEvent.mShift = (event.xkey.state & ShiftMask) != 0;
                post_event_from_user(LynxEvent);
            }
            break;

            case ClientMessage:
            {
                char* atom = XGetAtomName(mDisplay, event.xclient.message_type);
                if(*atom == *wmDeleteWindow)
                {
                    mLogManager->log("Quit message received.", ELL_INFORMATION);
                    mClose = true;
                }
                else
                {
                    UserEvent LynxEvent;
                    // we assume it's a user message
                    LynxEvent.mData1 = (int32_t)event.xclient.data.l[0];
                    LynxEvent.mData2 = (int32_t)event.xclient.data.l[1];
                    post_event_from_user(LynxEvent);
                }
                XFree(atom);
            }
            break;

            case SelectionRequest:
            {
                XEvent respond;
                XSelectionRequestEvent* req = &(event.xselectionrequest);
                if(req->target == XA_STRING)
                {
                    XChangeProperty(mDisplay,
                                    req->requestor,
                                    req->property, req->target,
                                    8,	  // format
                                    PropModeReplace,
                                    (unsigned char*) mClipboard.c_str(),
                                    mClipboard.size());
                    respond.xselection.property = req->property;
                }
                else if(req->target == X_ATOM_TARGETS)
                {
                    long data[2];

                    data[0] = X_ATOM_TEXT;
                    data[1] = XA_STRING;

                    XChangeProperty(mDisplay, req->requestor,
                                    req->property, req->target,
                                    8, PropModeReplace,
                                    (unsigned char *) &data,
                                    sizeof(data));
                    respond.xselection.property = req->property;
                }
                else
                {
                    respond.xselection.property= None;
                }
                respond.xselection.type= SelectionNotify;
                respond.xselection.display= req->display;
                respond.xselection.requestor= req->requestor;
                respond.xselection.selection=req->selection;
                respond.xselection.target= req->target;
                respond.xselection.time = req->time;
                XSendEvent(mDisplay, req->requestor,0,0,&respond);
                XFlush(mDisplay);
            }
            break;

            default:
                break;
            } // end switch

        } // end while
    }

    return !mClose;
}

//! notifies the device that it should close itself
void WindowManagerLinux_C::close()
{
    mClose = true;
}

//! Cause the device to temporarily pause execution and let other processes to run
void WindowManagerLinux_C::yield()
{
    struct timespec ts = {0,0};
    nanosleep(&ts, NULL);
}

//! Pause execution and let other processes to run for a specified amount of time.
void WindowManagerLinux_C::sleep(uint32_t timeMs, bool pauseTimer)
{
    const bool wasStopped = mTimeManager ? mTimeManager->is_virtual_clock_stopped() : true;

    struct timespec ts;
    ts.tv_sec = (time_t)(timeMs / 1000);
    ts.tv_nsec = (long)(timeMs % 1000) * 1000000;

    if(pauseTimer && !wasStopped)
        mTimeManager->stop_virtual_clock();

    nanosleep(&ts, NULL);

    if(pauseTimer && !wasStopped)
        mTimeManager->start_virtual_clock();
}

void WindowManagerLinux_C::set_window_caption(std::wstring caption)
{
    XTextProperty txt;
    const wchar_t* t = caption.c_str();
    if(Success==XwcTextListToTextProperty(mDisplay, const_cast<wchar_t** >(&t),
                                          1, XStdICCTextStyle, &txt))
    {
        XSetWMName(mDisplay, mWindow, &txt);
        XSetWMIconName(mDisplay, mWindow, &txt);
        XFree(txt.value);
    }
}

std::wstring WindowManagerLinux_C::get_window_caption(void) const
{
    return L"dfs";
}

bool WindowManagerLinux_C::is_window_focused(void) const
{
    return mWindowHasFocus;
}

bool WindowManagerLinux_C::is_window_fullscreen(void) const
{
    return mParameters.mFullscreen;
}

bool WindowManagerLinux_C::set_gamma_ramp(float red, float green, float blue, float brightness, float contrast)
{
    int32_t eventbase, errorbase;
    if(XF86VidModeQueryExtension(mDisplay, &eventbase, &errorbase))
    {
        XF86VidModeGamma gamma;
        gamma.red=red;
        gamma.green=green;
        gamma.blue=blue;
        XF86VidModeSetGamma(mDisplay, mScreennr, &gamma);
        return true;
    }
    return false;
}

bool WindowManagerLinux_C::get_gamma_ramp(float& red, float& green, float& blue, float& brightness, float& contrast)
{
    brightness = 0.f;
    contrast = 0.f;
    int32_t eventbase, errorbase;
    if(XF86VidModeQueryExtension(mDisplay, &eventbase, &errorbase))
    {
        XF86VidModeGamma gamma;
        XF86VidModeGetGamma(mDisplay, mScreennr, &gamma);
        red = gamma.red;
        green = gamma.green;
        blue = gamma.blue;
        return true;
    }
    return false;
}

const char* WindowManagerLinux_C::get_text_from_clipboard() const
{
    Window ownerWindow = XGetSelectionOwner(mDisplay, X_ATOM_CLIPBOARD);
    if(ownerWindow ==  mWindow)
    {
        return mClipboard.c_str();
    }
    mClipboard = "";
    if(ownerWindow != None)
    {
        XConvertSelection(mDisplay, X_ATOM_CLIPBOARD, XA_STRING, None, ownerWindow, CurrentTime);
        XFlush(mDisplay);

        // check for data
        Atom type;
        int format;
        unsigned long numItems, bytesLeft, dummy;
        unsigned char* data;
        XGetWindowProperty(mDisplay, ownerWindow,
                           XA_STRING, 	  // property name
                           0,		 // offset
                           0,	  	  // length (we only check for data, so 0)
                           0, 	 	  // Delete 0==false
                           AnyPropertyType,  // AnyPropertyType or property identifier
                           &type,		  // return type
                           &format,	  // return format
                           &numItems,   // number items
                           &bytesLeft,  // remaining bytes for partial reads
                           &data);		// data
        if(bytesLeft > 0)
        {
            // there is some data to get
            int result = XGetWindowProperty(mDisplay, ownerWindow, XA_STRING, 0,
                                            bytesLeft, 0, AnyPropertyType, &type, &format,
                                            &numItems, &dummy, &data);
            if(result == Success)
                mClipboard = (char*)data;
            XFree(data);
        }
    }

    return mClipboard.c_str();
}

void WindowManagerLinux_C::copy_to_clipboard(const char* text) const
{
    // Actually there is no clipboard on X but applications just say they own the clipboard and return text when asked.
    // Which btw. also means that on X you lose clipboard content when closing applications.
    mClipboard = text;
    XSetSelectionOwner(mDisplay, X_ATOM_CLIPBOARD, mWindow, CurrentTime);
    XFlush(mDisplay);
}

// return true if the passed event has the type passed in parameter arg
Bool PredicateIsEventType(Display* mDisplay, XEvent* event, XPointer arg)
{
    if(event && event->type == *(int*)arg)
        return True;
    return False;
}

void WindowManagerLinux_C::clear_system_messages(void)
{
    XEvent event;
    int usrArg = ButtonPress;
    while(XCheckIfEvent(mDisplay, &event, PredicateIsEventType, XPointer(&usrArg)) == True)
    {
        //
    }
    usrArg = ButtonRelease;
    while(XCheckIfEvent(mDisplay, &event, PredicateIsEventType, XPointer(&usrArg)) == True)
    {
        //
    }
    usrArg = MotionNotify;
    while(XCheckIfEvent(mDisplay, &event, PredicateIsEventType, XPointer(&usrArg)) == True)
    {
        //
    }
    usrArg = KeyRelease;
    while(XCheckIfEvent(mDisplay, &event, PredicateIsEventType, XPointer(&usrArg)) == True)
    {
        //
    }
    usrArg = KeyPress;
    while(XCheckIfEvent(mDisplay, &event, PredicateIsEventType, XPointer(&usrArg)) == True)
    {
        //
    }
}

void WindowManagerLinux_C::create_driver()
{
    VideoManager_C* t = new VideoManager_C(this, mParameters.mWindowSize);
    if(t->init_driver())
    {
        mVideoManager = t;
    }
    else
    {
        mVideoManager = 0;
        t->decrement_counter();
    }
}

bool WindowManagerLinux_C::create_window()
{
    mDisplay = XOpenDisplay(0);
    if(!mDisplay)
    {
        mLogManager->log("Error: Need running XServer to start Irrlicht Engine.", ELL_ERROR);
        if(XDisplayName(0)[0])
            mLogManager->log("Could not open mDisplay", XDisplayName(0), ELL_ERROR);
        else
            mLogManager->log("Could not open mDisplay, set DISPLAY variable", ELL_ERROR);
        return false;
    }

    mScreennr = DefaultScreen(mDisplay);

    switch_to_fullscreen();

    GLXFBConfig glxFBConfig;
    int major, minor;
    bool isAvailableGLX=false;

    isAvailableGLX=glXQueryExtension(mDisplay,&major,&minor);
    if(isAvailableGLX && glXQueryVersion(mDisplay, &major, &minor))
    {
        typedef GLXFBConfig *(* PFNGLXCHOOSEFBCONFIGPROC)(Display *dpy, int screen, const int *attrib_list, int *nelements);

        PFNGLXCHOOSEFBCONFIGPROC glxChooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC)glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXChooseFBConfig"));
        if(major==1 && minor>2 && glxChooseFBConfig)
        {
            // attribute array for the draw buffer
            int visualAttrBuffer[] =
            {
                GLX_RENDER_TYPE, GLX_RGBA_BIT,
                GLX_RED_SIZE, 4,
                GLX_GREEN_SIZE, 4,
                GLX_BLUE_SIZE, 4,
                GLX_ALPHA_SIZE, 1,
                GLX_DEPTH_SIZE, 16, //10,11
                GLX_DOUBLEBUFFER, True,
                GLX_STENCIL_SIZE, 1,
                GLX_SAMPLE_BUFFERS, 1,
                GLX_SAMPLES, mParameters.mAntiAliasing, // 18,19
                GLX_STEREO, False,
                None
            };

            GLXFBConfig* configList=0;
            int nitems=0;
            if(mParameters.mAntiAliasing<2)
            {
                visualAttrBuffer[17] = 0;
                visualAttrBuffer[19] = 0;
            }
            // first round with unchanged values
            {
                configList=glxChooseFBConfig(mDisplay, mScreennr, visualAttrBuffer,&nitems);
                if(!configList && mParameters.mAntiAliasing)
                {
                    while(!configList && (visualAttrBuffer[19]>1))
                    {
                        visualAttrBuffer[19] -= 1;
                        configList=glxChooseFBConfig(mDisplay, mScreennr, visualAttrBuffer,&nitems);
                    }
                    if(!configList)
                    {
                        visualAttrBuffer[17] = 0;
                        visualAttrBuffer[19] = 0;
                        configList=glxChooseFBConfig(mDisplay, mScreennr, visualAttrBuffer,&nitems);
                        if(configList)
                        {
                            mLogManager->log("No FSAA available.", ELL_WARNING);
                            mParameters.mAntiAliasing=0;
                        }
                        else
                        {
                            //reenable multisampling
                            visualAttrBuffer[17] = 1;
                            visualAttrBuffer[19] = mParameters.mAntiAliasing;
                        }
                    }
                }
            }
            if(configList)
            {
                glxFBConfig=configList[0];
                XFree(configList);
                mUseGLXWindow=true;
                typedef XVisualInfo *(* PFNGLXGETVISUALFROMFBCONFIGPROC)(Display *dpy, GLXFBConfig config);
                PFNGLXGETVISUALFROMFBCONFIGPROC glxGetVisualFromFBConfig= (PFNGLXGETVISUALFROMFBCONFIGPROC)glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXGetVisualFromFBConfig"));
                if(glxGetVisualFromFBConfig)
                    mVisual = glxGetVisualFromFBConfig(mDisplay,glxFBConfig);
            }
        }
        else
        {
            // attribute array for the draw buffer
            int visualAttrBuffer[] =
            {
                GLX_RGBA, GL_TRUE,
                GLX_RED_SIZE, 4,
                GLX_GREEN_SIZE, 4,
                GLX_BLUE_SIZE, 4,
                GLX_ALPHA_SIZE, 0,
                GLX_DEPTH_SIZE, 16,
                GLX_STENCIL_SIZE, 1, // 12,13
                // The following mAttributes have no flags, but are
                // either present or not. As a no-op we use
                // GLX_USE_GL, which is silently ignored by glXChooseVisual
                GLX_DOUBLEBUFFER, // 14
                GLX_USE_GL, // 15
                None
            };

            mVisual=glXChooseVisual(mDisplay, mScreennr, visualAttrBuffer);
            if(!mVisual)
            {
                visualAttrBuffer[13]=0; // stencil

                mVisual=glXChooseVisual(mDisplay, mScreennr, visualAttrBuffer);
                if(!mVisual)
                    mLogManager->log("No doublebuffering available.", ELL_ERROR);
            }
        }
    }
    else
        mLogManager->log("No GLX support available. OpenGL driver will not work.", ELL_WARNING);
    // don't use the XVisual with OpenGL, because it ignores all requested
    // properties of the mParameters

    if(!mVisual)
    {
        mLogManager->log("Fatal error, could not get mVisual.", ELL_ERROR);
        XCloseDisplay(mDisplay);
        mDisplay=0;
        return false;
    }
    // create color map
    Colormap colormap;
    colormap = XCreateColormap(mDisplay,
                               RootWindow(mDisplay, mVisual->screen),
                               mVisual->visual, AllocNone);

    mAttributes.colormap = colormap;
    mAttributes.border_pixel = 0;
    mAttributes.event_mask = StructureNotifyMask | FocusChangeMask | ExposureMask;
    mAttributes.event_mask |= PointerMotionMask |
                              ButtonPressMask | KeyPressMask |
                              ButtonReleaseMask | KeyReleaseMask;

    // create new Window
    // Remove mWindow manager decoration in fullscreen
    mAttributes.override_redirect = mParameters.mFullscreen;
    mWindow = XCreateWindow(mDisplay,
                            RootWindow(mDisplay, mVisual->screen),
                            0, 0, mWidth, mHeight, 0, mVisual->depth,
                            InputOutput, mVisual->visual,
                            CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect,
                            &mAttributes);
    XMapRaised(mDisplay, mWindow);
    Atom wmDelete;
    wmDelete = XInternAtom(mDisplay, wmDeleteWindow, True);
    XSetWMProtocols(mDisplay, mWindow, &wmDelete, 1);
    if(mParameters.mFullscreen)
    {
        XSetInputFocus(mDisplay, mWindow, RevertToParent, CurrentTime);
        int grabKb = XGrabKeyboard(mDisplay, mWindow, True, GrabModeAsync,
                                   GrabModeAsync, CurrentTime);
        int grabPointer = XGrabPointer(mDisplay, mWindow, True, ButtonPressMask,
                                       GrabModeAsync, GrabModeAsync, mWindow, None, CurrentTime);
        XWarpPointer(mDisplay, None, mWindow, 0, 0, 0, 0, 0, 0);
    }

    mWindowMinimized=false;
    // Currently broken in X, see Bug ID 2795321
    // XkbSetDetectableAutoRepeat(mDisplay, True, &AutorepeatSupport);

    // connect glx context to mWindow
    mContext=0;
    if(isAvailableGLX)
    {
        if(mUseGLXWindow)
        {
            mGlxWin=glXCreateWindow(mDisplay,glxFBConfig,mWindow,NULL);
            if(mGlxWin)
            {
                // create glx context
                mContext = glXCreateNewContext(mDisplay, glxFBConfig, GLX_RGBA_TYPE, NULL, True);
                if(mContext)
                {
                    if(!glXMakeContextCurrent(mDisplay, mGlxWin, mGlxWin, mContext))
                    {
                        mLogManager->log("Could not make context current.", ELL_WARNING);
                        glXDestroyContext(mDisplay, mContext);
                    }
                }
                else
                {
                    mLogManager->log("Could not create GLX rendering context.", ELL_WARNING);
                }
            }
            else
            {
                mLogManager->log("Could not create GLX mWindow.", ELL_WARNING);
            }
        }
        else
        {
            mContext = glXCreateContext(mDisplay, mVisual, NULL, True);
            if(mContext)
            {
                if(!glXMakeCurrent(mDisplay, mWindow, mContext))
                {
                    mLogManager->log("Could not make context current.", ELL_WARNING);
                    glXDestroyContext(mDisplay, mContext);
                }
            }
            else
            {
                mLogManager->log("Could not create GLX rendering context.", ELL_WARNING);
            }
        }
    }

    Window tmp;
    uint32_t borderWidth;
    int x,y;
    unsigned int bits;

    XGetGeometry(mDisplay, mWindow, &tmp, &x, &y, &mWidth, &mHeight, &borderWidth, &bits);
    mParameters.mWindowSize.X = mWidth;
    mParameters.mWindowSize.Y = mHeight;

    mStdHints = XAllocSizeHints();
    long num;
    XGetWMNormalHints(mDisplay, mWindow, mStdHints, &num);
    init_x_atoms();
    return true;
}

void WindowManagerLinux_C::create_keymap()
{
    // I don't know if this is the best method  to create
    // the lookuptable, but I'll leave it like that until
    // I find a better version.

    //mKeyMap.reallocate(84);
    mKeyMap.reserve(84);
    mKeyMap.push_back(SKeyMap(XK_BackSpace, KEY_BACK));
    mKeyMap.push_back(SKeyMap(XK_Tab, KEY_TAB));
    mKeyMap.push_back(SKeyMap(XK_Linefeed, 0)); // ???
    mKeyMap.push_back(SKeyMap(XK_Clear, KEY_CLEAR));
    mKeyMap.push_back(SKeyMap(XK_Return, KEY_RETURN));
    mKeyMap.push_back(SKeyMap(XK_Pause, KEY_PAUSE));
    mKeyMap.push_back(SKeyMap(XK_Scroll_Lock, KEY_SCROLL));
    mKeyMap.push_back(SKeyMap(XK_Sys_Req, 0)); // ???
    mKeyMap.push_back(SKeyMap(XK_Escape, KEY_ESCAPE));
    mKeyMap.push_back(SKeyMap(XK_Insert, KEY_INSERT));
    mKeyMap.push_back(SKeyMap(XK_Delete, KEY_DELETE));
    mKeyMap.push_back(SKeyMap(XK_Home, KEY_HOME));
    mKeyMap.push_back(SKeyMap(XK_Left, KEY_LEFT));
    mKeyMap.push_back(SKeyMap(XK_Up, KEY_UP));
    mKeyMap.push_back(SKeyMap(XK_Right, KEY_RIGHT));
    mKeyMap.push_back(SKeyMap(XK_Down, KEY_DOWN));
    mKeyMap.push_back(SKeyMap(XK_Prior, KEY_PRIOR));
    mKeyMap.push_back(SKeyMap(XK_Page_Up, KEY_PRIOR));
    mKeyMap.push_back(SKeyMap(XK_Next, KEY_NEXT));
    mKeyMap.push_back(SKeyMap(XK_Page_Down, KEY_NEXT));
    mKeyMap.push_back(SKeyMap(XK_End, KEY_END));
    mKeyMap.push_back(SKeyMap(XK_Begin, KEY_HOME));
    mKeyMap.push_back(SKeyMap(XK_Num_Lock, KEY_NUMLOCK));
    mKeyMap.push_back(SKeyMap(XK_KP_Space, KEY_SPACE));
    mKeyMap.push_back(SKeyMap(XK_KP_Tab, KEY_TAB));
    mKeyMap.push_back(SKeyMap(XK_KP_Enter, KEY_RETURN));
    mKeyMap.push_back(SKeyMap(XK_KP_F1, KEY_F1));
    mKeyMap.push_back(SKeyMap(XK_KP_F2, KEY_F2));
    mKeyMap.push_back(SKeyMap(XK_KP_F3, KEY_F3));
    mKeyMap.push_back(SKeyMap(XK_KP_F4, KEY_F4));
    mKeyMap.push_back(SKeyMap(XK_KP_Home, KEY_HOME));
    mKeyMap.push_back(SKeyMap(XK_KP_Left, KEY_LEFT));
    mKeyMap.push_back(SKeyMap(XK_KP_Up, KEY_UP));
    mKeyMap.push_back(SKeyMap(XK_KP_Right, KEY_RIGHT));
    mKeyMap.push_back(SKeyMap(XK_KP_Down, KEY_DOWN));
    mKeyMap.push_back(SKeyMap(XK_Print, KEY_PRINT));
    mKeyMap.push_back(SKeyMap(XK_KP_Prior, KEY_PRIOR));
    mKeyMap.push_back(SKeyMap(XK_KP_Page_Up, KEY_PRIOR));
    mKeyMap.push_back(SKeyMap(XK_KP_Next, KEY_NEXT));
    mKeyMap.push_back(SKeyMap(XK_KP_Page_Down, KEY_NEXT));
    mKeyMap.push_back(SKeyMap(XK_KP_End, KEY_END));
    mKeyMap.push_back(SKeyMap(XK_KP_Begin, KEY_HOME));
    mKeyMap.push_back(SKeyMap(XK_KP_Insert, KEY_INSERT));
    mKeyMap.push_back(SKeyMap(XK_KP_Delete, KEY_DELETE));
    mKeyMap.push_back(SKeyMap(XK_KP_Equal, 0)); // ???
    mKeyMap.push_back(SKeyMap(XK_KP_Multiply, KEY_MULTIPLY));
    mKeyMap.push_back(SKeyMap(XK_KP_Add, KEY_ADD));
    mKeyMap.push_back(SKeyMap(XK_KP_Separator, KEY_SEPARATOR));
    mKeyMap.push_back(SKeyMap(XK_KP_Subtract, KEY_SUBTRACT));
    mKeyMap.push_back(SKeyMap(XK_KP_Decimal, KEY_DECIMAL));
    mKeyMap.push_back(SKeyMap(XK_KP_Divide, KEY_DIVIDE));
    mKeyMap.push_back(SKeyMap(XK_KP_0, KEY_0));
    mKeyMap.push_back(SKeyMap(XK_KP_1, KEY_1));
    mKeyMap.push_back(SKeyMap(XK_KP_2, KEY_2));
    mKeyMap.push_back(SKeyMap(XK_KP_3, KEY_3));
    mKeyMap.push_back(SKeyMap(XK_KP_4, KEY_4));
    mKeyMap.push_back(SKeyMap(XK_KP_5, KEY_5));
    mKeyMap.push_back(SKeyMap(XK_KP_6, KEY_6));
    mKeyMap.push_back(SKeyMap(XK_KP_7, KEY_7));
    mKeyMap.push_back(SKeyMap(XK_KP_8, KEY_8));
    mKeyMap.push_back(SKeyMap(XK_KP_9, KEY_9));
    mKeyMap.push_back(SKeyMap(XK_F1, KEY_F1));
    mKeyMap.push_back(SKeyMap(XK_F2, KEY_F2));
    mKeyMap.push_back(SKeyMap(XK_F3, KEY_F3));
    mKeyMap.push_back(SKeyMap(XK_F4, KEY_F4));
    mKeyMap.push_back(SKeyMap(XK_F5, KEY_F5));
    mKeyMap.push_back(SKeyMap(XK_F6, KEY_F6));
    mKeyMap.push_back(SKeyMap(XK_F7, KEY_F7));
    mKeyMap.push_back(SKeyMap(XK_F8, KEY_F8));
    mKeyMap.push_back(SKeyMap(XK_F9, KEY_F9));
    mKeyMap.push_back(SKeyMap(XK_F10, KEY_F10));
    mKeyMap.push_back(SKeyMap(XK_F11, KEY_F11));
    mKeyMap.push_back(SKeyMap(XK_F12, KEY_F12));
    mKeyMap.push_back(SKeyMap(XK_Shift_L, KEY_LSHIFT));
    mKeyMap.push_back(SKeyMap(XK_Shift_R, KEY_RSHIFT));
    mKeyMap.push_back(SKeyMap(XK_Control_L, KEY_LCONTROL));
    mKeyMap.push_back(SKeyMap(XK_Control_R, KEY_RCONTROL));
    mKeyMap.push_back(SKeyMap(XK_Caps_Lock, KEY_CAPITAL));
    mKeyMap.push_back(SKeyMap(XK_Shift_Lock, KEY_CAPITAL));
    mKeyMap.push_back(SKeyMap(XK_Meta_L, KEY_LWIN));
    mKeyMap.push_back(SKeyMap(XK_Meta_R, KEY_RWIN));
    mKeyMap.push_back(SKeyMap(XK_Alt_L, KEY_LMENU));
    mKeyMap.push_back(SKeyMap(XK_Alt_R, KEY_RMENU));
    mKeyMap.push_back(SKeyMap(XK_ISO_Level3_Shift, KEY_RMENU));
    mKeyMap.push_back(SKeyMap(XK_Menu, KEY_MENU));
    mKeyMap.push_back(SKeyMap(XK_space, KEY_SPACE));
    mKeyMap.push_back(SKeyMap(XK_exclam, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_quotedbl, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_section, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_numbersign, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_dollar, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_percent, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_ampersand, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_apostrophe, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_parenleft, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_parenright, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_asterisk, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_plus, KEY_PLUS)); //?
    mKeyMap.push_back(SKeyMap(XK_comma, KEY_COMMA)); //?
    mKeyMap.push_back(SKeyMap(XK_minus, KEY_MINUS)); //?
    mKeyMap.push_back(SKeyMap(XK_period, KEY_PERIOD)); //?
    mKeyMap.push_back(SKeyMap(XK_slash, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_0, KEY_0));
    mKeyMap.push_back(SKeyMap(XK_1, KEY_1));
    mKeyMap.push_back(SKeyMap(XK_2, KEY_2));
    mKeyMap.push_back(SKeyMap(XK_3, KEY_3));
    mKeyMap.push_back(SKeyMap(XK_4, KEY_4));
    mKeyMap.push_back(SKeyMap(XK_5, KEY_5));
    mKeyMap.push_back(SKeyMap(XK_6, KEY_6));
    mKeyMap.push_back(SKeyMap(XK_7, KEY_7));
    mKeyMap.push_back(SKeyMap(XK_8, KEY_8));
    mKeyMap.push_back(SKeyMap(XK_9, KEY_9));
    mKeyMap.push_back(SKeyMap(XK_colon, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_semicolon, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_less, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_equal, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_greater, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_question, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_at, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_mu, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_EuroSign, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_A, KEY_A));
    mKeyMap.push_back(SKeyMap(XK_B, KEY_B));
    mKeyMap.push_back(SKeyMap(XK_C, KEY_C));
    mKeyMap.push_back(SKeyMap(XK_D, KEY_D));
    mKeyMap.push_back(SKeyMap(XK_E, KEY_E));
    mKeyMap.push_back(SKeyMap(XK_F, KEY_F));
    mKeyMap.push_back(SKeyMap(XK_G, KEY_G));
    mKeyMap.push_back(SKeyMap(XK_H, KEY_H));
    mKeyMap.push_back(SKeyMap(XK_I, KEY_I));
    mKeyMap.push_back(SKeyMap(XK_J, KEY_J));
    mKeyMap.push_back(SKeyMap(XK_K, KEY_K));
    mKeyMap.push_back(SKeyMap(XK_L, KEY_L));
    mKeyMap.push_back(SKeyMap(XK_M, KEY_M));
    mKeyMap.push_back(SKeyMap(XK_N, KEY_N));
    mKeyMap.push_back(SKeyMap(XK_O, KEY_O));
    mKeyMap.push_back(SKeyMap(XK_P, KEY_P));
    mKeyMap.push_back(SKeyMap(XK_Q, KEY_Q));
    mKeyMap.push_back(SKeyMap(XK_R, KEY_R));
    mKeyMap.push_back(SKeyMap(XK_S, KEY_S));
    mKeyMap.push_back(SKeyMap(XK_T, KEY_T));
    mKeyMap.push_back(SKeyMap(XK_U, KEY_U));
    mKeyMap.push_back(SKeyMap(XK_V, KEY_V));
    mKeyMap.push_back(SKeyMap(XK_W, KEY_W));
    mKeyMap.push_back(SKeyMap(XK_X, KEY_X));
    mKeyMap.push_back(SKeyMap(XK_Y, KEY_Y));
    mKeyMap.push_back(SKeyMap(XK_Z, KEY_Z));
    mKeyMap.push_back(SKeyMap(XK_Adiaeresis, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_Odiaeresis, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_Udiaeresis, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_bracketleft, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_backslash, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_bracketright, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_asciicircum, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_degree, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_underscore, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_grave, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_acute, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_quoteleft, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_a, KEY_A));
    mKeyMap.push_back(SKeyMap(XK_b, KEY_B));
    mKeyMap.push_back(SKeyMap(XK_c, KEY_C));
    mKeyMap.push_back(SKeyMap(XK_d, KEY_D));
    mKeyMap.push_back(SKeyMap(XK_e, KEY_E));
    mKeyMap.push_back(SKeyMap(XK_f, KEY_F));
    mKeyMap.push_back(SKeyMap(XK_g, KEY_G));
    mKeyMap.push_back(SKeyMap(XK_h, KEY_H));
    mKeyMap.push_back(SKeyMap(XK_i, KEY_I));
    mKeyMap.push_back(SKeyMap(XK_j, KEY_J));
    mKeyMap.push_back(SKeyMap(XK_k, KEY_K));
    mKeyMap.push_back(SKeyMap(XK_l, KEY_L));
    mKeyMap.push_back(SKeyMap(XK_m, KEY_M));
    mKeyMap.push_back(SKeyMap(XK_n, KEY_N));
    mKeyMap.push_back(SKeyMap(XK_o, KEY_O));
    mKeyMap.push_back(SKeyMap(XK_p, KEY_P));
    mKeyMap.push_back(SKeyMap(XK_q, KEY_Q));
    mKeyMap.push_back(SKeyMap(XK_r, KEY_R));
    mKeyMap.push_back(SKeyMap(XK_s, KEY_S));
    mKeyMap.push_back(SKeyMap(XK_t, KEY_T));
    mKeyMap.push_back(SKeyMap(XK_u, KEY_U));
    mKeyMap.push_back(SKeyMap(XK_v, KEY_V));
    mKeyMap.push_back(SKeyMap(XK_w, KEY_W));
    mKeyMap.push_back(SKeyMap(XK_x, KEY_X));
    mKeyMap.push_back(SKeyMap(XK_y, KEY_Y));
    mKeyMap.push_back(SKeyMap(XK_z, KEY_Z));
    mKeyMap.push_back(SKeyMap(XK_ssharp, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_adiaeresis, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_odiaeresis, 0)); //?
    mKeyMap.push_back(SKeyMap(XK_udiaeresis, 0)); //?

    sort(mKeyMap.begin(), mKeyMap.end());
}

void WindowManagerLinux_C::init_x_atoms()
{
    X_ATOM_CLIPBOARD = XInternAtom(mDisplay, "CLIPBOARD", False);
    X_ATOM_TARGETS = XInternAtom(mDisplay, "TARGETS", False);
    X_ATOM_UTF8_STRING = XInternAtom(mDisplay, "UTF8_STRING", False);
    X_ATOM_TEXT = XInternAtom(mDisplay, "TEXT", False);
}

bool WindowManagerLinux_C::switch_to_fullscreen(bool reset)
{
    if(!mParameters.mFullscreen)
        return true;
    if(reset)
    {
        if(mParameters.mFullscreen)
        {
            XF86VidModeSwitchToMode(mDisplay, mScreennr, &mOldVideoMode);
            XF86VidModeSetViewPort(mDisplay, mScreennr, 0, 0);
            XFlush(mDisplay);
        }
        return true;
    }

    /////////////////////////////////////////////////////////////////////////////////////getVideoModeList();
    int32_t eventbase, errorbase;
    int32_t bestMode = -1;

    if(XF86VidModeQueryExtension(mDisplay, &eventbase, &errorbase))
    {
        // enumerate video modes
        int32_t modeCount;
        XF86VidModeModeInfo** modes;

        XF86VidModeGetAllModeLines(mDisplay, mScreennr, &modeCount, &modes);

        // save current video mode
        mOldVideoMode = *modes[0];

        // find fitting mode
        for(int32_t i = 0; i<modeCount; ++i)
        {
            if(bestMode==-1 && modes[i]->hdisplay >= mWidth && modes[i]->vdisplay >= mHeight)
                bestMode = i;
            else if(bestMode!=-1 &&
                    modes[i]->hdisplay >= mWidth &&
                    modes[i]->vdisplay >= mHeight &&
                    modes[i]->hdisplay < modes[bestMode]->hdisplay &&
                    modes[i]->vdisplay < modes[bestMode]->vdisplay)
                bestMode = i;
        }
        if(bestMode != -1)
        {
            mLogManager->log("Starting fullscreen mode...", ELL_INFORMATION);
            XF86VidModeSwitchToMode(mDisplay, mScreennr, modes[bestMode]);
            XF86VidModeSetViewPort(mDisplay, mScreennr, 0, 0);
        }
        else
        {
            mLogManager->log("Could not find specified video mode, running windowed.", ELL_WARNING);
            mParameters.mFullscreen = false;
        }

        XFree(modes);
    }
    return mParameters.mFullscreen;
}

} // namespace Lynx

#endif // _LYNX_LINUX_
